﻿using System;
using System.Collections;

namespace WebDemo.Services
{
    public abstract class DomainObject<T>
    {
        protected internal abstract T OnUse(DomainContext context, object state = null);
    }

    public interface IDomainContext : IDisposable
    {
        Hashtable Data { get; }
        event EventHandler<DomainObjectEventArgs> Before;
        event EventHandler<DomainObjectEventArgs> After;
        event EventHandler<DomainObjectErrorEventArgs> Error;
        T Use<T>(DomainObject<T> domainObject, object state = null);
    }

    public class DomainContext : IDomainContext
    {
        public Hashtable Data { get; }
        public event EventHandler<DomainObjectEventArgs> Before;
        public event EventHandler<DomainObjectEventArgs> After;
        public event EventHandler<DomainObjectErrorEventArgs> Error;

        public DomainContext()
        {
            Data = new Hashtable();
        }

        protected virtual void OnBefore(DomainObjectEventArgs e)
        {
            Before?.Invoke(this, e);
        }

        protected virtual void OnAfter(DomainObjectEventArgs e)
        {
            After?.Invoke(this, e);
        }

        protected virtual void OnError(DomainObjectErrorEventArgs e)
        {
            Error?.Invoke(this, e);
        }

        public virtual T Use<T>(DomainObject<T> domainObject, object state = null)
        {
            var e = new DomainObjectEventArgs
            {
                DomainObject = domainObject,
                Context = this,
                Result = NoReturn.Value
            };
            OnBefore(e);

            if (e.Result == NoReturn.Value)
            {
                try
                {
                    e.Result = domainObject.OnUse(e.Context, state);
                }
                catch (Exception ex)
                {
                    var err = new DomainObjectErrorEventArgs
                    {
                        DomainObject = e.DomainObject,
                        Context = e.Context,
                        Error = ex
                    };
                    OnError(err);
                    throw;
                }
                finally
                {
                    OnAfter(e);
                }
            }
            else
            {
                OnAfter(e);
            }
            return (T) e.Result;
        }

        private class NoReturn
        {
            public static readonly NoReturn Value;

            static NoReturn()
            {
                Value = new NoReturn();
            }

            private NoReturn()
            {

            }
        }

        public virtual void Dispose()
        {
            Data.Clear();
            Before = null;
            After = null;
            Error = null;
        }
    }
}